<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>添加删除卡片动态效果</title>
    <style>
        @layer view-transitions {

            /* 不捕获根元素，允许在卡片动画时进行指针交互 */
            @layer no-root {
                :root {
                    view-transition-name: none;
                }

                ::view-transition {
                    pointer-events: none;
                }
            }

            /* 卡片在移动到新位置时，一般应使用弹跳效果 */
            @layer reorder-cards {
                @supports (view-transition-class: card) {
                    .warning {
                        display: none;
                    }

                    :root {
                        --bounce-easing: linear(0,
                                0.004,
                                0.016,
                                0.035,
                                0.063,
                                0.098,
                                0.141 13.6%,
                                0.25,
                                0.391,
                                0.563,
                                0.765,
                                1,
                                0.891 40.9%,
                                0.848,
                                0.813,
                                0.785,
                                0.766,
                                0.754,
                                0.75,
                                0.754,
                                0.766,
                                0.785,
                                0.813,
                                0.848,
                                0.891 68.2%,
                                1 72.7%,
                                0.973,
                                0.953,
                                0.941,
                                0.938,
                                0.941,
                                0.953,
                                0.973,
                                1,
                                0.988,
                                0.984,
                                0.988,
                                1);
                    }

                    .card {
                        view-transition-class: card;
                    }

                    /* 没有view-transition-class时，你需要编写一个选择器来匹配所有卡片 … 并且每次添加/删除卡片时都需要更新该选择器 */
                    ::view-transition-group(*.card) {
                        animation-timing-function: var(--bounce-easing);
                        animation-duration: 0.5s;
                    }
                }
            }

            /* 新添加的卡片应进行动画进入 */
            @layer add-card {
                @keyframes animate-in {
                    0% {
                        opacity: 0;
                        translate: 0 -200px;
                    }

                    100% {
                        opacity: 1;
                        translate: 0 0;
                    }
                }

                ::view-transition-new(targeted-card):only-child {
                    animation: animate-in ease-in 0.25s;
                }
            }

            /* 被移除的卡片应进行动画退出 */
            @layer remove-card {
                @keyframes animate-out {
                    0% {
                        opacity: 1;
                        translate: 0 0;
                    }

                    100% {
                        opacity: 0;
                        translate: 0 -200px;
                    }
                }

                ::view-transition-old(targeted-card):only-child {
                    animation: animate-out ease-out 0.5s;
                }
            }
        }

        /* 基础样式 */
        @layer base {
            * {
                box-sizing: border-box;
            }

            body {
                display: grid;
                height: 90dvh;
                place-items: center;
                padding: 2rem 0;
                font-family: system-ui, sans-serif;
                width: 100%;
            }

            a {
                color: blue;

                &:hover,
                &:focus {
                    color: navy;
                }
            }

            .cards {
                padding: 0;
                display: flex;
                justify-content: center;
                width: 100%;
                gap: 2rem;
                padding: 1rem 2rem;
                overflow-y: auto;
                overscroll-behavior: contain;
            }

            .card {
                width: 100%;
                aspect-ratio: 2/3;
                display: block;
                position: relative;
                border-radius: 1rem;
                max-width: 10vw;
                min-width: 50px;
                background-color: grey;
            }

            .delete-btn {
                --icon: url();
                position: absolute;
                bottom: -0.75rem;
                right: -0.75rem;
                width: 3rem;
                height: 3rem;
                padding: 0.5rem;
                border: 4px solid;
                border-radius: 100%;
                background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
                color: white;
                cursor: pointer;

                &:hover {
                    background-color: orangered;
                }
            }

            .add-btn {
                --icon: url();
                width: 3rem;
                height: 3rem;
                padding: 0.5rem;
                border: 4px solid;
                border-radius: 100%;
                background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
                color: white;
                cursor: pointer;

                &:hover {
                    background-color: cornflowerblue;
                }
            }

            .sr-only {
                border: 0;
                clip: rect(1px, 1px, 1px, 1px);
                clip-path: inset(50%);
                height: 1px;
                margin: -1px;
                overflow: hidden;
                padding: 0;
                position: absolute;
                width: 1px;
                white-space: nowrap;
            }

            footer {
                text-align: center;
                font-style: italic;
                line-height: 1.42;
            }
        }

        @layer warning {
            .warning {
                padding: 1em;
                margin: 1em 0;
                border: 1px solid #ccc;
                background: rgba(255 255 205 / 0.8);
                text-align: center;
            }

            .warning> :first-child {
                margin-top: 0;
            }

            .warning> :last-child {
                margin-bottom: 0;
            }

            .warning a {
                color: blue;
            }

            .warning--info {
                border: 1px solid #123456;
                background: rgb(205 230 255 / 0.8);
            }

            .warning--alarm {
                border: 1px solid red;
                background: #ff000010;
            }
        }
    </style>
</head>

<body>
    <button class="add-btn">
        <span class="sr-only">添加</span>
    </button>
    <template id="card">
        <li class="card">
            <button class="delete-btn">
                <span class="sr-only">删除</span>
            </button>
        </li>
    </template>
    <ul class="cards">
        <li class="card" style="view-transition-name: card-1; background-color: tan;">
            <button class="delete-btn">
                <span class="sr-only">删除</span>
            </button>
        </li>
        <li class="card" style="view-transition-name: card-2; background-color: khaki;">
            <button class="delete-btn">
                <span class="sr-only">删除</span>
            </button>
        </li>
        <li class="card" style="view-transition-name: card-3; background-color: thistle;">
            <button class="delete-btn">
                <span class="sr-only">删除</span>
            </button>
        </li>
        <li class="card" style="view-transition-name: card-4; background-color: wheat;">
            <button class="delete-btn">
                <span class="sr-only">删除</span>
            </button>
        </li>
    </ul>

    <script>
        // 核心逻辑
        document.querySelector(".cards").addEventListener("click", (e) => {
            if (e.target.classList.contains("delete-btn")) {
                e.target.parentElement.remove();
            }
        });
        document.querySelector(".add-btn").addEventListener("click", async (e) => {
            const template = document.getElementById("card");
            const $newCard = template.content.cloneNode(true);
            $newCard.firstElementChild.style.backgroundColor = `#${Math.floor(
                Math.random() * 16777215
            ).toString(16)}`;
            document.querySelector(".cards").appendChild($newCard);
        });
        // 使用MutationObserver设置自动视图转换
        // - 添加了卡片
        //   ~> 撤销添加，然后重新添加并包装在VT中
        // - 移除了卡片
        //   ~> 撤销移除，然后重新移除并包装在VT中
        if (document.startViewTransition) {
            let isLocked = false;
            const observer = new MutationObserver(async (mutations) => {
                if (isLocked) return;
                for (const mutation of mutations) {
                    // 添加了卡片
                    if (mutation.addedNodes.length) {
                        const $li = Array.from(mutation.addedNodes).find((n) => n.nodeName == "LI");
                        if (!$li) return;
                        isLocked = true; // 防止循环
                        // VT 自定义
                        $li.style.viewTransitionName = "targeted-card";
                        // 撤销添加，然后重新添加并包装在VT中
                        $li.remove();
                        const t = document.startViewTransition(() => {
                            mutation.target.insertBefore($li, mutation.nextSibling);
                        });
                        await t.ready;
                        const rand =
                            window.performance.now().toString().replace(".", "_") +
                            Math.floor(Math.random() * 1000);
                        $li.style.viewTransitionName = `card-${rand}`;
                        isLocked = false;
                    }
                    // 移除了卡片
                    if (mutation.removedNodes.length) {
                        const $li = Array.from(mutation.removedNodes).find(
                            (n) => n.nodeName == "LI"
                        );
                        if (!$li) return;
                        isLocked = true; // 防止循环
                        // VT 自定义
                        $li.style.viewTransitionName = "targeted-card";
                        // 重新添加节点，然后包装在VT中移除
                        mutation.target.insertBefore($li, mutation.previousSibling);
                        const t = document.startViewTransition(() => {
                            $li.remove();
                        });
                        await t.ready;
                        isLocked = false;
                    }
                }
            });
            observer.observe(document.querySelector(".cards"), {
                childList: true,
                characterData: false
            });
        }
    </script>
</body>

</html>